########################################################################
#  Pyfm - personal finance data-base
#  Copyright (C) 2007,8 Ehud Ben-Reuven
#  udi@benreuven.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
########################################################################
"""Report functions
====================
report tools on the data-base
"""
import logging
from obj.obj import db
from obj.dt import DT
from obj.flat import sobj,add2line,line_copy
from model import *
from tagsyntax import *
from lib import conf
from lib.quoteinterface import getFX
import copy

def pstr(msg,obj):
    if not obj: return msg
    if not isinstance(obj,list): obj=[obj]
    for o in obj:
        msg+=' '+sobj(o)
    return msg
def pinfo(msg,obj=None):
    logging.info('%s',pstr(msg,obj))
def pwarn(msg,obj=None):
    logging.warning('W %s',pstr(msg,obj))
def perr(msg,obj=None):
    logging.error('ERROR %s',pstr(msg,obj))
    
def holdingcur(holding):
    for a in holding.Asset:
        if a.CURRENCY: return a.CURRENCY[0]
    return holding['Account'][0]['CURDEF'][0]

def getCheckingPrice(h,cur,dt):
    """get UNITPRICE of a checking account in "cur",
its 1 unless cur!=ORIGCURRENCY in which case it is the exchange rate from ORIGCURRENCY to cur.
dt is the date for which you want the UNITPRICE value and dtxrate is the best date (latest before dt)
from which the value was taken"""
    if not cur: return (None,None)
    if not h.isfixedprice(): return (None,None)
    p=h.getfixedprice()
    if not p: return (None,None)
    origcur=None
    for a in h.Asset:
        if a.ORIGCURRENCY:
            origcur=a.ORIGCURRENCY[0]
            break
    xrate=None
    dtxrate=None
    if origcur:
        xrate,dtxrate=getFX(dt,origcur,cur)
        return (p*xrate,dtxrate)
    else:
        return (None,None)
    
def getunitpricecur(historytrans,holding=None):
    """Extract UNITPRICE from a History or Transaction object and find what is its currency"""
    cur=None
    unitprice=None
    if not historytrans:
        if holding:
            cur = holdingcur(holding)
            unitprice,dtdummy=getCheckingPrice(holding,cur,None)
    else:    
        if not holding: holding=historytrans.HoldingSource[0].Holding[0]
        cur = historytrans['CURRENCY']
        if cur:
            cur=cur[0]
        else:
            cur = holdingcur(holding)

        if historytrans.UNITPRICE:
            unitprice=historytrans.UNITPRICE[0]
            if isinstance(historytrans,Transaction):
                if historytrans.MARKUP: unitprice+=historytrans.MARKUP[0]
                if historytrans.MARKDOWN: unitprice-=historytrans.MARKDOWN[0]  
        elif holding.isfixedprice():
            if isinstance(historytrans,History):
                dt=historytrans.DTASOF[0]
            elif isinstance(historytrans,Transaction):
                dt=historytrans.DTSETTLE[0]
            else:
                raise Exception('Bad type')
            unitprice,dtdummy=getCheckingPrice(holding,cur,dt)
    return (unitprice,cur)

def getcurdef(line):
    """Get CURDEF param"""
    return line.pop('CURDEF',[conf.get('CURDEF')])[0]

def getfilter(line,tag,default={}):
    filter=line.pop(tag,[default])
    if len(filter)!=1: raise Exception('Cant handle more than one %s filter'%tag)
    return line_copy(filter[0])

def getassets(line):
    asset_filter=getfilter(line,'Asset')
    return db.getallobjects(Asset,asset_filter)

def getholdings(line):
    holding_filter=getfilter(line,'Holding')
    return db.getallobjects(Holding,holding_filter)
    
def scurdef(curdef,cur):
    if not (curdef and cur): return curdef  or cur or ''
    s=str(curdef)
    if cur != curdef:
        return s+'('+str(cur)+')'
    else:
        return s

def getstatement_filter(line,dtre=None,default={'HoldingSource':[{'FILE':['_MERGED_']}]}):
    statement_filter=getfilter(line,'Statement',default=default)
    if dtre:
        add2line(statement_filter,'!DTASOF','MAX')
        add2line(statement_filter,'<DTASOF',dtre)
    return statement_filter

def getholdingsources(holding,file='_MERGED_'):
    return db.getallobjects(HoldingSource,{'FILE':[file],'Holding':[holding]})

def getstatements(statement_filter,h=None,hs=None):
    if hs:
        statement_filter['HoldingSource']=hs
    elif h:
        for hs in statement_filter.setdefault('HoldingSource',[{}]):
            hs['Holding']=[h]
    return db.getallobjects(Statement,statement_filter)

def getstatement(statement_filter,h=None,hs=None):
    statement=getstatements(statement_filter,h,hs)
    if not statement: return
    statement=statement[0]
    if not (statement.UNITS and abs(statement.UNITS[0])>0.5): return
    return statement

def expandhistory(history,h):
    #assert unitprice!=None
    dtpriceasof = history['DTASOF'][0] if history else None

    unitprice,cur=getunitpricecur(history,h)

    if not cur:
        cur = holdingcur(h)

    if unitprice==None:
        return None,None,cur
    assert cur # You cant have price without currency
    return dtpriceasof,unitprice,cur

def gethistories(h,dtstart,dtend):
    return db.getallobjects(History,{'!DTASOF':['SORT'],
                    '<DTASOF':[dtend],
                    '>=DTASOF':[dtstart],
                    'Asset':h['Asset']}) # Its possible to have more than one Asset, which is OK because they will be ORed
    
def gethistory(h,dtasof):
    history=db.getallobjects(History,{'!DTASOF':['MAX'],
                    '<=DTASOF':[dtasof],
                    'Asset':h['Asset']})  # Its possible to have more than one Asset, which is OK because they will be ORed
    if history:
        history=history[0]
        dt,unitprice,cur=expandhistory(history,h)
    else:
        cur = holdingcur(h)
        unitprice,dt=getCheckingPrice(h,cur,dtasof)
    return dt,unitprice,cur

def getcomposition(h,dtasof):
    """Return the percentage of the asset in Equity, Bond and Cash.
using the latest available information up to dtasof.
Any difference of the sum of the percentage to 100 is "Other"
"""
    assert isinstance(h,Holding)
    UNCLASSIFIED='unclassified'
    c=db.getalllines({'Asset':h['Asset'],
                      '?COMPONENT':[],
                      '!DTASOF':['MAX'],
                      '<=DTASOF':[dtasof],
                    }) #It's possible to have more than one
                                         # Asset, which is OK because they will
                                         # be ORed
    if c and c[0]:
        d = dict(c1.explode() for c1 in c[0]['COMPONENT']) # There is just one
        # result (we did DTASOF!MAX)
        d[UNCLASSIFIED]=100.-sum(d.values())
        return d
        
    if h.isfixedprice():
        component='cash'
    else:
        ac=h.getassetclass()
        if 'BOND' in ac:
            component='bond'
        elif 'STOCK' in ac:
            component='equity'
            if 'LARGESTOCK' in ac or 'LARGE' in ac:
                component+='.large'
            elif 'MEDIUM' in ac:
                component+='.medium'
            elif 'SMALLSTOCK' in ac or 'SMALL' in ac:
                component+='.small'
            else:
                component+='.unknown_size'
            if 'VALUE' in ac:
                component+='.value'
            elif 'GROWTH' in ac:
                component+='.growth'
            else:
                component+='.blend'
        elif 'HF' in ac:
            component='hf'
        elif 'PE' in ac:
            component='pe'
        elif 'OPT' in ac:
            component='option'
        elif 'REAL-ESTATE' in ac:
            component='real_estate'
        else:
            component=UNCLASSIFIED
    return {component:100.}

def getprice(h,dtasof):
    # TODO get  quote when needed/possible
    return gethistory(h,dtasof)

def popdtstartend(line):
    """Extract DTEND and DTSTART from line.
This pair is usually used to filter dates as follows: date>=DTSTART and date<DTEND
By default DTEND is NOW and DTSTART is 1-Jan of the same year as DTEND.
However if DTEND=1-Jan of a year then DTSTART will be 1-Jan of the previouse year"""
    dtend=line.pop('DTEND',[DTASOF('NOW')])[0]
    # default for DTSTART is start of year
    dtstart=copy.copy(dtend)
    dtstart.startofyear()
    if dtstart==dtend:
        dtstart-=1
        dtstart.startofyear()
    dtstart=line.pop('DTSTART',[dtstart])[0]
    return dtstart,dtend

def fix_price_currency(line):
    """Convert UNITPRICE currency to be that of the ORIGCURRENCY of the asset.
TODO: The date used for the convertion is a tricky
"""
    try:
        # Needed both for ORIGCURRENCY and CURRENCY
        a=line['Asset']
        if not a: a=line['Holding'][0].Asset
        
        origcur=line['ORIGCURRENCY']
        if not origcur:
            origcur=list(set(oc for aa in a for oc in aa.ORIGCURRENCY))
        assert len(origcur)==1
        origcur=origcur[0]
        
        cur=line['CURRENCY']
        if not cur:
            cur=list(set(c for aa in a for c in aa.CURRENCY))
        assert len(cur)==1
        cur=cur[0]
        
        if cur != origcur:
            for tag in ['DTSETTLE', 'DTTRADE', 'DTASOF']:
                try:
                    dt=line[tag][0]
                    break
                except KeyError: pass
            line['UNITPRICE']=[line['UNITPRICE'][0]*getFXrate(dt,cur,origcur)]
            line['CURRENCY']=[origcur] # this is last so line is'nt modified if UNITPRICE is missing
    except KeyError: pass # Don't do anything, if any of the needed tags is missing
